/*
 * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke
 *
 * This file is part of SeMoLearning.
 *
 * SeMoLearning is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * SeMoLearning is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SeMoLearning.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef DBDRIVER_H_
#define DBDRIVER_H_

#include "semol/core/SemolExp.h" // DLL export/import defines

#include "semol/core/Parameters.h"
#include "semol/core/Node.h"

#include <sqlite3.h>
#include <vector>
#include <list>
#include <string>

class UMutex;

namespace semol {

class SEMOL_EXP DBDriver {
public:
	DBDriver(const ParametersMap & parameters = ParametersMap());
	virtual ~DBDriver();

	void parseParameters(const ParametersMap & parameters);
	void setDbInMemory(bool dbInMemory);
	void setJournalMode(int journalMode);
	void setCacheSize(unsigned int cacheSize);
	void setSynchronous(int synchronous);
	void setTempStore(int tempStore);
	void setImagesCompressed(bool imagesCompressed);

	// Multi threading safe access, lock/unlock db access
	void lock() const;
	void unlock() const;

	const std::string & getPath() const {return _path;}

	bool openConnection(const std::string & url, bool overwritten = false);
	void closeConnection();
	bool isConnected() const;

	void beginTransaction() const;
	void commit() const;

	void addStatistics(int stMemSize, int lastNodeAdded, int processMemUsed, int databaseMemUsed) const;

	void executeNoResult(const std::string & sql) const;

	// Update
	void changeWordsRef(const std::map<int, int> & refsToChange); // <oldWordId, activeWordId>
	void deleteWords(const std::vector<int> & ids);

	//Save objects
	void saveOrUpdate(const std::vector<Node *> & nodes) const;
	void saveOrUpdate(const std::vector<Sensor *> & sensors) const;

	// Load objects
	std::map<int, Sensor*>  loadLastSensors() const;
	std::list<Node *>       loadLastNodes() const;
	Node *                  loadNode(int nodeId) const;
	std::list<Node *>       loadNodes(const std::list<int> & ids) const;
	std::map<int, Sensor *> loadSensors(const std::set<int> & sensorIds, bool withRawData = false) const;

	// Specific queries...
	std::list<SensorRaw>    getRawData(int id) const;
	SensorRaw 				getSensorRawData(int sensorId) const;
	std::list<Actuator>     getActuatorData(int id) const;
	std::set<int>           getNeighborIds(int nodeId, bool onlyWithActions = false) const;
	NeighborsMultiMap       getNeighbors(int nodeId) const;
	int                     getWeight(int nodeId) const;
	void                    getLoopClosureIds(int nodeId, std::set<int> & loopIds, std::set<int> & childIds) const;
	std::set<int>           getAllNodeIds() const;
	int                     getLastNodeId() const;
	int                     getLastSensorId() const;
	std::vector<int>        getSensors(int nodeId) const;
	long                    getMemoryUsed() const; // In bytes
	NeighborLink *			loadLink(int actuatorId) const;

private:
	void save(const std::list<Sensor *> & sensors) const;
	void update(const std::list<Sensor *> & sensors) const;
	void update(const std::list<Node *> & nodes) const;
	void save(const std::list<Node *> & nodes) const;

	int getLastId(const std::string & tableName) const;
	void loadLinks(std::list<Node *> & nodes) const;

private:
	std::string queryStepNode() const;
	std::string queryStepSensor() const;
	std::string queryStepNodeToSensor() const;
	std::string queryStepLink() const;
	std::string queryStepActuator() const;
	std::string queryStepSensorsChanged() const;
	std::string queryStepSensors() const;
	void stepNode(sqlite3_stmt * ppStmt, const Node * s) const;
	void stepSensor(sqlite3_stmt * ppStmt, const Sensor & sensor) const;
	void stepNodeToSensor(sqlite3_stmt * ppStmt, int nodeId, int sensorId, int num) const;
	void stepLink(sqlite3_stmt * ppStmt, int fromId, int toId, int type, int actuator_id, const std::list<std::vector<int> > & sensorHistory) const;
	void stepActuator(sqlite3_stmt * ppStmt, int id, int num, const Actuator & actuator) const;
	void stepSensorsChanged(sqlite3_stmt * ppStmt, int nodeId, int oldWordId, int newWordId) const;

private:
	int loadOrSaveDb(sqlite3 *pInMemory, const std::string & fileName, int isSave) const;

private:
	sqlite3 * _ppDb;
	bool _dbInMemory;
	unsigned int _cacheSize;
	int _journalMode;
	int _synchronous;
	int _tempStore;
	bool _imagesCompressed;

	std::string _path;
	UMutex * _dbMutex;
};

}

#endif /* DBDRIVER_H_ */
